Skip to content

Lazily initialize members in NativeJavaObject/NativeJavaMethod#2262

Open
ZZZank wants to merge 9 commits intomozilla:masterfrom
ZZZank:lazy-java-member-init
Open

Lazily initialize members in NativeJavaObject/NativeJavaMethod#2262
ZZZank wants to merge 9 commits intomozilla:masterfrom
ZZZank:lazy-java-member-init

Conversation

@ZZZank
Copy link
Contributor

@ZZZank ZZZank commented Jan 18, 2026

This PR is made for implementing serialization of NativeJavaMethod, and provide another possible solution to #2248 .

  1. serialization of NativeJavaMethod (and its subclass NativeJavaField)

To support serialization of them, a new field parent is added, representing the class that holds this member, which will then be used for getting the correct JavaMembers during initialization.

But initialization will not happen immediately after deserialization, instead, initialization is deferred to the first usage, so that initialization will happen after the scope used for getting JavaMembers have been deserialized enough.

  1. Lazy initialization of members in NativeJavaObject

Similar to NativeJavaMethod, its initialization is deferred to the first usage. For me this is a valid solution to #2248 , but I'm not familiar with all the scope thingy after all, so reviews and analysis are welcomed.

You may have noticed that there's no thread safety measure, and I'm not sure if this actually matters. From my perspective JavaMembers is read-only, and identity of JavaMembers and members of JavaMembers doesn't matter.

@ZZZank ZZZank marked this pull request as ready for review January 18, 2026 12:27
@aardvark179
Copy link
Contributor

I like the laziness approach in general (we have a lot of reflected members that are never normally used but consume memory), but it hasn't gone far enough yet to resolve the serialisation issues. Have a look at the extra assertions I've added to ContinuationsApiTest in #2249 to see where we can hit a problem with function identity. There's also some existing and deeply weird behaviour on these classes associated with setting additional properties.

I think the solution might be to make JavaMembers more pure, i.e. it only supplies the necessary NativeJava* objects, but never stores them itself (this would allow the generic types to be tightened up), and would never itself be serialised. We would then change classes like NativeJavaObject to be the ones that hold the cached NativeJavaMethods etc. in their slot maps, and these could then be serialised in the normal way.

This would require a little work to handle sealed cases correctly, and raises a question of what the initial state of the property map should be. Maybe we should initially gather a list of reflected member names, and populate them with values lazily?

@ZZZank
Copy link
Contributor Author

ZZZank commented Jan 24, 2026

... hit a problem with function identity

It seems this can be fixed by implementing a proper equals(...) for NativeJavaMethod

... also some existing and deeply weird behaviour on these classes associated with setting additional properties.

I don't quite understand this, is the code below a valid example of this issue?

let obj1, obj2; // instances of the same java class

obj1.someMethod.someProp = 42
obj2.someMethod.someProp // gives 42

My next step will probably be introduce some intermediate representation, between ExecutableBox and NativeJavaMethod, and eventually get rid of NativeJavaMethod within JavaMembers

@aardvark179
Copy link
Contributor

aardvark179 commented Jan 24, 2026

... hit a problem with function identity

It seems this can be fixed by implementing a proper equals(...) for NativeJavaMethod

NativeJavaMethod is a subclass of BaseFunction, it is mutable, I can add properties to it, change the prototype, whatever, you have to honour identity here.

... also some existing and deeply weird behaviour on these classes associated with setting additional properties.

I don't quite understand this, is the code below a valid example of this issue?

let obj1, obj2; // instances of the same java class

obj1.someMethod.someProp = 42
obj2.someMethod.someProp // gives 42

Yeah. If you look at NativeJavaObject it has a put implementation that looks like this:

    @Override
    public void put(String name, Scriptable start, Object value) {
        // We could be asked to modify the value of a property in the
        // prototype. Since we can't add a property to a Java object,
        // we modify it in the prototype rather than copy it down.
        if (prototype == null || members.has(name, false))
            members.put(this, name, javaObject, value, false);
        else prototype.put(name, prototype, value);
    }

We should probably treat these objects as sealed, i.e. you cannot add new properties to them, even you can edit existing ones.

My next step will probably be introduce some intermediate representation, between ExecutableBox and NativeJavaMethod, and eventually get rid of NativeJavaMethod within JavaMembers

I think we're in agreement here that NativeJavaMethod has no place in JavaMembers, the question is how does NativeJavaObject hold on to resolved methods, and how can you ensure the identity of those?

@aardvark179
Copy link
Contributor

Heh @ZZZank, would you be open to a PR to make JavaMembers a pure cache for member boxes and similar, and never serialised? I'm happy to work on it if so.

@ZZZank
Copy link
Contributor Author

ZZZank commented Feb 8, 2026

#2298 , though I'm not sure whether NativeJavaMethod reference should also be stripped, sinve we will never mutate this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants